﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Net;
using System.Reflection;
using System.Security.AccessControl;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using System.Linq;

namespace ImvuCacheViewer
{
    public partial class Form1 {

        private Dictionary<int, Participant> _avatars = new Dictionary<int, Participant>();
        List<GetMessage> textMessages = new List<GetMessage>();
        int myID = 0;
        string myAviName = string.Empty;

        private void btnShowLog_Click(object sender, EventArgs e) {
            string logFilename = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
                                           "IMVU\\IMVULog.log");
            List<string> filenames = new List<string>();
            for (int i = 5; i >0 ; i--) {
                filenames.Add(logFilename + "." + i);
            }
            filenames.Add(logFilename);
            textMessages = new List<GetMessage>();
            _avatars = new Dictionary<int, Participant>();
            lblParsing.Visible = true;
            lblParsing.Refresh();
            gridParticipantProducts.Rows.Clear();
            foreach (string filename in filenames)
            {
                if (!File.Exists(filename)) continue;
                ProcessLogFile(filename);
            }            
            UpdateLogs();
        }

        private void ProcessLogFile(string logFilename) {

            try {
                using (FileStream fs = new FileStream(logFilename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) {
                    StreamReader sr = new StreamReader(fs);
                    const string sUserID = " 'userId': ";
                    const string sAvatarName = " 'avatarName': ";
                    int iLine = 1;

                    while (!sr.EndOfStream) {
                        string line = sr.ReadLine();
                        if (line == null) continue;
                        Debug.WriteLine(iLine++ + "(" + line.Length + "): " + line);

                        if (line.StartsWith(sUserID)) {
                            string idString = line.Substring(sUserID.Length).Replace(",", string.Empty);
                            int.TryParse(idString, out myID);
                        }

                        if (line.StartsWith(sAvatarName)) {
                            myAviName = line.Substring(sUserID.Length + 4)
                                .Replace(",", string.Empty)
                                .Replace("'", string.Empty);
                        }

                        if (line.Contains("INFO: __participantJoinedListener():")) {
                            Participant pc = new Participant(line);
                            _avatars[pc.userId] = pc;
                        }

                        bool isTheirMessage = Regex.IsMatch(line, @"INFO: received \d+ new messages:");
                        bool isMyMessage = Regex.IsMatch(line, @"INFO: sendChatMessages: batching up \d+ calls:");
                        if (isTheirMessage || isMyMessage) {
                            IEnumerable<GetMessage> messages = GetMessage.GetArray(line);
                            foreach (GetMessage message in messages) {
                                if (message.message == null) continue;
                                if (isMyMessage) {
                                    message.from_id = myID;
                                }
                                if (!message.message.StartsWith("*")) {
                                    if (message.chatId == 0)
                                    {
                                        Match match = Regex.Match(line, @"chat (?<chatId>\d+)", RegexOptions.ExplicitCapture);
                                        if (match.Success)
                                        {
                                            message.chatId = int.Parse(match.Groups["chatId"].Captures[0].Value);
                                        }
                                    }
                                    textMessages.Add(message);
                                } else {
                                    if (message.message.StartsWith("*use") || message.message.StartsWith("*putOnOutfit")) {
                                        if (_avatars.ContainsKey(message.from_id)) {
                                            IEnumerable<int> ints = ParseOMatic.GetInts(message.message);
                                            foreach (int i in ints)
                                            {
                                                if (!_avatars[message.from_id].ProductIDs.Contains(i))
                                                    _avatars[message.from_id].ProductIDs.Add(i);
                                            }

                                        }
                                    }

                                    if (message.message.StartsWith("*boot")) {
                                        IEnumerable<int> ints = ParseOMatic.GetInts(message.message);
                                        int[] array = ints.ToArray();
                                        if (array.Length == 2) {
                                            message.message = "-BOOTS- ";
                                            if (array[1] == myID) {
                                                message.message += myAviName;
                                            } else {
                                                string aviName = "(User " + message.from_id + ")";
                                                if (_avatars.ContainsKey(array[1])) {
                                                    aviName = _avatars[array[1]].avatarName;
                                                }
                                                message.message += aviName;
                                            }
                                            textMessages.Add(message);
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
            catch (Exception e) {
                MessageBox.Show("Exception: " + e.Message, "Oops", MessageBoxButtons.OK, MessageBoxIcon.Warning);
            }

        }

        private void UpdateLogs() {
            lblParsing.Visible = false;

            // parsed file, now display results
            txtLog.Text = string.Empty;
            StringBuilder sb = new StringBuilder();
            foreach (GetMessage textMessage in textMessages)
            {
                string line;
                if (textMessage.from_id == myID)
                {
                    line = myAviName + ": " + textMessage.message;
                }
                else
                {
                    string sAvi = "(User " + textMessage.from_id + ")";
                    if (_avatars.ContainsKey(textMessage.from_id))
                    {
                        sAvi = _avatars[textMessage.from_id].avatarName;
                    }
                    line = sAvi + ": " + textMessage.message;
                }
                sb.AppendLine(line);
            }
            string chatText = sb.ToString();
            chatText = chatText.Replace("(User " + myID + ")", myAviName);
            txtLog.Text = chatText;

            cboParticipants.Items.Clear();
            cboParticipants.Items.AddRange(_avatars.Values.OrderBy(p => p.avatarName).ToArray());
            picParticipant.Image = null;
            txtParticipant.Text = string.Empty;
            if (cboParticipants.Items.Count > 0)
            {
                cboParticipants.SelectedIndex = 0;
            }

        }

        private void UpdateParticipant() {
            Participant pc = cboParticipants.SelectedItem as Participant;
            if (pc == null) return;
            gridParticipantProducts.Rows.Clear();
            StringBuilder sb = new StringBuilder();
            sb.AppendLine((pc.isGuest ? "Guest_" : string.Empty) + pc.avatarName);
            sb.AppendLine("Gender: " + pc.gender);
            sb.AppendLine("Age: " + pc.age);
            sb.AppendLine("DOB: " + pc.dob.ToString("yyyy-MM-dd"));
            sb.AppendLine("userId: " + pc.userId);
            // www.imvu.com/catalog/web_mypage.php?user=#####
            txtParticipant.Text = sb.ToString();

            //add product rows.
            
            foreach (int productID in pc.ProductIDs) {
                DataGridViewRow row = new DataGridViewRow();
                DataGridViewTextBoxCell idCell = new DataGridViewTextBoxCell();
                idCell.Value = productID;
                row.Cells.Add(idCell);
                DataGridViewLinkCell nameCell = new DataGridViewLinkCell();
                string filename = Path.Combine(ProgCachePath, productID + ".info");
                if (File.Exists(filename)) {
                    using (StreamReader sr = File.OpenText(filename)) {
                        string[] pieces = sr.ReadToEnd().Split('\t');
                        nameCell.Value = pieces[1] + " by " + pieces[2];
                    }
                } else {
                    nameCell.Value = "Product " + productID;
                }
                row.Cells.Add(nameCell);
                gridParticipantProducts.Rows.Add(row);
            }

            WebClient wc = new WebClient();
            try
            {
                byte[] bytes = wc.DownloadData(pc.picUrl);
                picParticipant.Image = Image.FromStream(new MemoryStream(bytes));
            }
            catch (Exception exc)
            {
                txtParticipant.Text += "Image: Exception: " + exc.Message + Environment.NewLine;
            }
        }

        private void cboParticipants_SelectedIndexChanged(object sender, EventArgs e) {
        }


        private void cboParticipants_SelectedValueChanged(object sender, EventArgs e) {
            UpdateParticipant();
        }

        public class Chat {
            private int ID { get; set; }
            public List<Participant> Participants { get; private set; }
            public List<GetMessage> Messages { get; private set; }
            public Chat() {
                Participants = new List<Participant>();
                Messages = new List<GetMessage>();
            }
        }

        private List<Chat> chats = new List<Chat>();

        public abstract class ParsedObject {
            protected ParsedObject() {}
            protected ParsedObject(string line) {
                DoParse(line);
            }

            protected void DoParse(string line) {
                Dictionary<string, object> props = ParseOMatic.Parse(line);
                foreach (PropertyInfo info in this.GetType().GetProperties()) {
                    if (props.ContainsKey(info.Name)) {
                        try {
                            info.SetValue(this, props[info.Name], null);
                        } catch (Exception) { }
                    }
                }
            }
        }

        public class Participant: ParsedObject {
            public bool hasVIPPass { get; set; }
            public bool hasAccessPass { get; set; }
            public DateTime dob { get; set; }
            public string gender { get; set; }
            public int age { get; set; }
            public string avatarName { get; set; }
            public string picUrl { get; set; }
            public int userId { get; set; }
            public bool isGuest { get; set; }
            public HashSet<int> ProductIDs { get; private set; }
            public Participant() {
                ProductIDs = new HashSet<int>();
            }
            public Participant(string line): base(line) {
                ProductIDs = new HashSet<int>();
            }
            public override string ToString() {
                return avatarName;
            }
        }

        public class GetMessage: ParsedObject {
            public int chatId { get; set; }
            public int lastMessageId { get; set; }
            public int userId { get; set; }
            public int from_id { get; set; }
            public string message { get; set; }
            public int message_id { get; set; }
            public int to_id { get; set; }
            public GetMessage() {}
            public GetMessage(string line): base(line) {}
            public static IEnumerable<GetMessage> GetArray(string line) {
                List<GetMessage> list = new List<GetMessage>();
                List<Dictionary<string, object>> parsedList = ParseOMatic.ParseArray(line);
                foreach (Dictionary<string, object> props in parsedList) {
                    GetMessage message = new GetMessage();
                    foreach (PropertyInfo info in message.GetType().GetProperties()) {
                        if (props.ContainsKey(info.Name)) {
                            try {
                                info.SetValue(message, props[info.Name], null);
                            } catch (Exception) { }
                        }
                    }
                    list.Add(message);
                }
                return list;
            }
        }
        
        public class ParseOMatic {
            public static IEnumerable<int> GetInts(string line) {
                const string pattern = @"(?<int>\d+)";
                List<int> result = new List<int>();
                int parsed = 0;
                foreach (Match match in Regex.Matches(line, pattern, RegexOptions.ExplicitCapture)) {
                    foreach (Capture capture in match.Groups["int"].Captures) {
                        if (int.TryParse(capture.Value, out parsed)) {
                            result.Add(parsed);
                        }
                    }
                }
                return result;
            }

            public static List<Dictionary<string, object>> ParseArray(string line) {
                List<Dictionary<string, object>> result = new List<Dictionary<string, object>>();
                const string pattern = @"\[(\{(?<piece>.*?)\}(, )?)+\]";
                MatchCollection matches = Regex.Matches(line, pattern, RegexOptions.ExplicitCapture);
                foreach (Match match in matches) {
                    if (match.Groups["piece"].Captures.Count == 0) continue;
                    result.AddRange(from Capture capture in match.Groups["piece"].Captures select Parse(capture.Value));
                }

                return result;
            }


            public static Dictionary<string, object> Parse(string line) {
                const string pattern = @"'(?<key>\w+)': (?<value>[^\{\},]+)";
                MatchCollection matches = Regex.Matches(line, pattern, RegexOptions.ExplicitCapture);
                Dictionary<string, object> result = new Dictionary<string, object>();
                foreach (Match match in matches)
                {
                    Group groupKey = match.Groups["key"];
                    Group groupValue = match.Groups["value"];
                    if (groupKey.Captures.Count == 0) continue;
                    if (groupValue.Captures.Count == 0) continue;
                    Capture capKey = groupKey.Captures[0];
                    Capture capValue = groupValue.Captures[0];
                    string sKey = capKey.Value;
                    string sValue = capValue.Value;
                    object oValue;
                    int iValue;
                    if ((sValue.StartsWith("'") || sValue.StartsWith("u'")) && sValue.EndsWith("'")) {
                        int aposPos = sValue.IndexOf("'");
                        sValue = sValue.Substring(1 + aposPos, sValue.Length - 2 - aposPos);
                        if (int.TryParse(sValue, out iValue)) {
                            oValue = iValue;
                        } else {
                            DateTime dt;
                            if (DateTime.TryParse(sValue, out dt)) {
                                oValue = dt;
                            } else  {
                                oValue = sValue;
                            }
                        }
                    } else {
                        bool parsed = int.TryParse(sValue, out iValue);
                        if (parsed) oValue = iValue; else oValue = null;
                    }
                    result[sKey] = oValue;
                }
                return result;
            }
        }
    }

}